using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input.Touch;

namespace Xzipit.Utilities.Touch
{
    public static class SimultaneousGestureHelper
    {
        private const int MAX_TOUCHPOINTS_WINDOWS = 4;
        private const float MIN_DRAG_REQ = 1; //Measured in pixels
        private const float MAX_HDRAG_YLIMIT = 10; //Measured in pixels
        private const float MAX_VDRAG_XLIMIT = 10; // Measured in pixels
        private const float DOUBLETAP_LIMIT = 20;
        private static TimeSpan HOLD_TIME_REQ = new TimeSpan(0, 0, 0, 0, 250); //About 1 second
        private static TimeSpan FLICK_TIME_LIMIT = new TimeSpan(0, 0, 0, 0, 100); //0.1 seconds
        private static TimeSpan TIME_BETWEEN_TAPS_LIMIT = new TimeSpan(0, 0, 0, 0, 500); //0.5 seconds
        private static Queue<SimpleGestureSample> _gestureBuffer = new Queue<SimpleGestureSample>(MAX_TOUCHPOINTS_WINDOWS);
        private static Dictionary<int, SimpleGestureSample> _gestureStorage = new Dictionary<int, SimpleGestureSample>(MAX_TOUCHPOINTS_WINDOWS);

        private struct PrevTap
        {
            public Vector2 Position;
            public TimeSpan Timestamp;

            public PrevTap(Vector2 position, TimeSpan timestamp)
            {
                Position = position;
                Timestamp = timestamp;
            }
        }

        private static List<PrevTap> _lastTapPositions = new List<PrevTap>(MAX_TOUCHPOINTS_WINDOWS * 2);
        private static List<PrevTap> _removalHelper = new List<PrevTap>();
        
        public static SimpleGestureType EnabledGestures = SimpleGestureType.FreeDrag | SimpleGestureType.DoubleTap | 
            SimpleGestureType.Hold | SimpleGestureType.HorizontalDrag | SimpleGestureType.VerticalDrag | SimpleGestureType.Tap | 
            SimpleGestureType.DragComplete;

        public static bool IsGestureAvailable
        {
            get
            {
                return _gestureBuffer.Count > 0;
            }
        }

        public static SimpleGestureSample ReadGesture()
        {
            return _gestureBuffer.Dequeue();
        }

        public static void GenerateGestures()
        {
            foreach (PrevTap pt in _lastTapPositions)
            {
                if (DateTime.Now.TimeOfDay - pt.Timestamp > TIME_BETWEEN_TAPS_LIMIT)
                {
                    _removalHelper.Add(pt);
                }
            }

            foreach (PrevTap r in _removalHelper)
            {
                _lastTapPositions.Remove(r);
            }

            _gestureBuffer.Clear();
            TouchCollection touches = TouchPanel.GetState();
            TimeSpan curTime = new TimeSpan(DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second, DateTime.Now.Millisecond);

            for (int i = 0; i < touches.Count; i++)
            {
                switch (touches[i].State)
                {
                    case TouchLocationState.Pressed:
                        _gestureStorage.Add(
                            touches[i].Id, 
                            new SimpleGestureSample(
                                touches[i].Id, 
                                SimpleGestureType.None, 
                                curTime,
                                touches[i].Position, 
                                Vector2.Zero));
                        break;
                    case TouchLocationState.Moved:
                        UpdateMoved(touches[i], curTime);
                        break;
                    case TouchLocationState.Released:
                        UpdateReleased(touches[i], curTime);
                        break;
                }
            }

            foreach (SimpleGestureSample g in _gestureStorage.Values)
            {
                if (g.SimpleGestureType != SimpleGestureType.None)
                    _gestureBuffer.Enqueue(g);
            }
        }

        private static void UpdateMoved(TouchLocation tl, TimeSpan curTime)
        {
            SimpleGestureSample g;
            if (_gestureStorage.TryGetValue(tl.Id, out g))
            {
                float xDiff = Math.Abs(tl.Position.X - g.Position.X);
                float yDiff = Math.Abs(tl.Position.Y - g.Position.Y);

                // Drag Cases
                if (xDiff > MIN_DRAG_REQ || yDiff > MIN_DRAG_REQ)
                {
                    if (EnabledGestures.HasFlag(SimpleGestureType.HorizontalDrag) && yDiff < MAX_HDRAG_YLIMIT)
                    {
                        Vector2 hDelta = (g.SimpleGestureType == SimpleGestureType.None)? Vector2.Zero : tl.Position - g.Position;
                        hDelta.Y = 0;

                        _gestureStorage.Remove(tl.Id);
                        _gestureStorage.Add(
                            tl.Id, 
                            new SimpleGestureSample(
                                tl.Id, 
                                SimpleGestureType.HorizontalDrag,
                                curTime, 
                                tl.Position,
                                hDelta));
                    }
                    else if (EnabledGestures.HasFlag(SimpleGestureType.VerticalDrag) && xDiff < MAX_VDRAG_XLIMIT)
                    {
                        Vector2 vDelta = (g.SimpleGestureType == SimpleGestureType.None) ? Vector2.Zero : tl.Position - g.Position;
                        vDelta.X = 0;

                        _gestureStorage.Remove(tl.Id);
                        _gestureStorage.Add(
                            tl.Id, 
                            new SimpleGestureSample(
                                tl.Id, 
                                SimpleGestureType.VerticalDrag,
                                curTime, 
                                tl.Position,
                                vDelta));
                    }
                    else if (EnabledGestures.HasFlag(SimpleGestureType.FreeDrag))
                    {
                        _gestureStorage.Remove(tl.Id);
                        _gestureStorage.Add(
                            tl.Id,
                            new SimpleGestureSample(
                                tl.Id,
                                SimpleGestureType.FreeDrag,
                                curTime,
                                tl.Position,
                                (g.SimpleGestureType == SimpleGestureType.None) ? Vector2.Zero : tl.Position - g.Position));
                    }
                    else
                    {
                        _gestureStorage.Remove(tl.Id);
                    }
                }
                //Tap Cases
                else
                {
                    if (curTime - g.Timestamp >= HOLD_TIME_REQ)
                    {
                        if (EnabledGestures.HasFlag(SimpleGestureType.Hold))
                        {
                            _gestureStorage.Remove(tl.Id);
                            _gestureBuffer.Enqueue(
                                new SimpleGestureSample(tl.Id,
                                    SimpleGestureType.Hold,
                                    curTime,
                                    g.Position,
                                    Vector2.Zero));
                        }
                        else
                        {
                            _gestureStorage.Remove(tl.Id);
                        }
                    }
                }
            }
        }

        private static void UpdateReleased(TouchLocation tl, TimeSpan curTime)
        {
            SimpleGestureSample g;
            if (_gestureStorage.TryGetValue(tl.Id, out g))
            {
                if (g.SimpleGestureType == SimpleGestureType.FreeDrag || 
                    g.SimpleGestureType == SimpleGestureType.HorizontalDrag || 
                    g.SimpleGestureType == SimpleGestureType.VerticalDrag)
                {
                    if (EnabledGestures.HasFlag(SimpleGestureType.DragComplete))
                    {
                        _gestureStorage.Remove(tl.Id);
                        _gestureBuffer.Enqueue(
                            new SimpleGestureSample(
                                tl.Id,
                                SimpleGestureType.DragComplete,
                                curTime,
                                Vector2.Zero,
                                Vector2.Zero));
                    }
                    else
                    {
                        _gestureStorage.Remove(tl.Id);
                    }
                }
                else
                {
                    if (EnabledGestures.HasFlag(SimpleGestureType.DoubleTap) || EnabledGestures.HasFlag(SimpleGestureType.Tap))
                    {
                        PrevTap t = new PrevTap();
                        bool isSet = false;

                        if (EnabledGestures.HasFlag(SimpleGestureType.DoubleTap))
                        {
                            foreach (PrevTap prevTap in _lastTapPositions)
                            {
                                //System.Diagnostics.Debug.WriteLine(Math.Abs(prevTap.Position.X - tl.Position.X) + ">" + DOUBLETAP_LIMIT + " and " + 
                                if (Math.Abs(prevTap.Position.X - tl.Position.X) < DOUBLETAP_LIMIT && 
                                    Math.Abs(prevTap.Position.Y - tl.Position.Y) < DOUBLETAP_LIMIT)
                                {
                                    t = prevTap;
                                    isSet = true;
                                    break;
                                }
                            }
                        }

                        if (isSet)
                        {
                            _lastTapPositions.Remove(t);
                            _gestureStorage.Remove(tl.Id);
                            _gestureBuffer.Enqueue(
                                new SimpleGestureSample(
                                    tl.Id,
                                    SimpleGestureType.DoubleTap,
                                    curTime,
                                    tl.Position,
                                    Vector2.Zero));
                        }
                        else
                        {
                            if (EnabledGestures.HasFlag(SimpleGestureType.DoubleTap))
                                _lastTapPositions.Add(new PrevTap(tl.Position, DateTime.Now.TimeOfDay));

                            _gestureStorage.Remove(tl.Id);
                            if (EnabledGestures.HasFlag(SimpleGestureType.Tap))
                            {
                                _gestureBuffer.Enqueue(
                                    new SimpleGestureSample(
                                        tl.Id,
                                        SimpleGestureType.Tap,
                                        curTime,
                                        tl.Position,
                                        Vector2.Zero));
                            }
                        }
                    }
                }
            }
        }
    }
}
